#pragma once

//	The version of DirectInput to use.
#ifndef DIRECTINPUT_VERSION
	#define DIRECTINPUT_VERSION 0X800
#endif
#include <dinput.h>

#pragma comment(lib, "dinput8.lib")
#pragma comment(lib, "dxguid.lib")

#include <vector>
using std::vector;

//	Forward declarations:
class CDIKeyboard;
class CDIMouse;

//	Enumerations:
//		Devices:
enum DeviceFlags { DI_KEYBOARD = 1, DI_MOUSE = 2 };

//		Directions
enum Directions { DIR_LEFT = 0, DIR_RIGHT = 1, DIR_UP = 2, DIR_DOWN = 3, DIR_MAX };

//		Mouse buttons
enum MouseButtons { MOUSE_LEFT = 0, MOUSE_RIGHT = 1, MOUSE_MIDDLE = 2, MOUSE_MAX };

class CDirectInput
{
private:
	LPDIRECTINPUT8				m_lpDIObject;	//	DirectInput COM Object
	CDIKeyboard*			m_pKeyboard;	//	Keyboard object
	CDIMouse*				m_pMouse;		//	Mouse object

	static CDirectInput	m_Instance;
	CDirectInput(void);
	CDirectInput(const CDirectInput&);
	CDirectInput& operator=(const CDirectInput&);
	~CDirectInput(void);
	bool InitKeyboard(HWND hWnd, bool bIsExclusive = false);
	bool InitMouse(HWND hWnd, bool bIsExclusive = false);

public:
	static CDirectInput* GetInstance(void);
	bool InitDirectInput(HWND hWnd, HINSTANCE hInstance, unsigned int unInitDevices, unsigned int unExclusiveDevices = 0);
	void ShutdownDirectInput(void);
	void ReadDevices(void);
	void ClearInput(void);
	void AcquireAll(void);
	void UnacquireAll(void);
	bool KeyDown(unsigned char ucDIKey);
	bool KeyPressed(unsigned char ucDIKey);
	bool KeyPressedEx(unsigned char ucDIKey);
	bool KeyUp(unsigned char ucDIKey);
	bool KeyReleased(unsigned char ucDIKey);
	bool KeyReleasedEx(unsigned char ucDIKey); 
	char CheckKeys(void);
	char CheckBufferedKeysEx(void);
	unsigned char GetDIKCode(void);
	unsigned char GetBufferedDIKCodeEx(void);
	bool MouseButtonDown(unsigned char ucButton);
	bool MouseButtonPressed(unsigned char ucButton);
	bool MouseButtonPressedEx(unsigned char ucButton);
	bool MouseButtonUp(unsigned char ucButton);
	bool MouseButtonReleased(unsigned char ucButton);
	bool MouseButtonReleasedEx(unsigned char ucButton);
	int MouseCheckBufferedButtons(void);
	int MouseCheckBufferedButtonsEx(void);
	long MouseMovementX(void);
	long MouseMovementY(void);
	long MouseWheelMovement(void);
	int MouseGetNumButtons(void);
	int MouseGetPosX(void);
	int MouseGetPosY(void);
	void MouseSetPosX(int nPosX);
	void MouseSetPosY(int nPosY);

};

const int g_knBUFFER_SIZE = 10;	//	Arbitrary number of elements for Buffered Input.

//	Interface class defining "blue print" functions.
class ICDirectInputDevice
{
public:
	virtual ~ICDirectInputDevice(void) = 0 {};

	virtual bool ReadDevice(void) = 0;
	virtual bool ReadBufferedDevice(void) = 0;
	
	virtual bool Acquire(void) = 0;
	virtual bool Unacquire(void) = 0;

}; 	


//	Abstract class with partial functionality.
class CDirectInputDevice : public ICDirectInputDevice
{
protected:
	LPDIRECTINPUTDEVICE8	m_lpDevice;					//	DirectInput Device pointer.
	
	//	For DirectX Buffered input:
	DIDEVICEOBJECTDATA		m_didod[g_knBUFFER_SIZE];	//	Receives buffered data for the device.
	DWORD					m_dwBufferElements;			//	Number of element in the buffer for the device.

	//	Utility function to clear buffered data.
	void ClearBufferedData(void)
	{
		memset(&m_didod, 0, sizeof(m_didod));	//	clear out device object data.
		m_dwBufferElements = g_knBUFFER_SIZE;	//	set buffer elements to the size of the array.
	}

public:
	//	ConStructor.
	CDirectInputDevice(void)
	{
		m_lpDevice = NULL;

		ClearBufferedData();
	}

	//	Destructor.
	virtual ~CDirectInputDevice(void)
	{
		if (m_lpDevice)
		{
			m_lpDevice->Unacquire();
			if (m_lpDevice)
			{
				m_lpDevice->Release();
				m_lpDevice = NULL;
			}
		}
	}

	//	Common way to read DirectInput buffered data.
	///////////////////////////////////////////////////////////////////
	//	Return	:	true if it can read the device data, false if it can't.
	//	Purpose	:	Gets the DirectInput Buffered Data.
	//	NOTE	:	For all Ex functions.
	virtual bool ReadBufferedDevice(void)
	{
		//	Make sure the device was made.
		if (!m_lpDevice)
			return false;

		ClearBufferedData();

		//	Attempt to read the device...
		if (FAILED( m_lpDevice->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), m_didod, &m_dwBufferElements, 0) ))
		{
			//	If we couldn't, try to re-acquire the device.
			if (FAILED( m_lpDevice->Acquire() ))
				return false; //	Could not re-acquire the Device.

			ClearBufferedData();

			//	Now try reading it
			if (FAILED( m_lpDevice->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), m_didod, &m_dwBufferElements, 0) ))
				return false;
		}

		//	Success
		return true;
	}
	
	//	returns true if the device was able to be acquired.
	virtual bool Acquire(void)		{ return (m_lpDevice) ? SUCCEEDED( m_lpDevice->Acquire() ) : false; }

	//	returns true if the device was able to be unacquired.
	virtual bool Unacquire(void)	{ return (m_lpDevice) ? SUCCEEDED( m_lpDevice->Unacquire() ) : false; }

};

//////////////////////////////////////////////////////////////////////////////////
//	CSGD_DIKeyboard
//////////////////////////////////////////////////////////////////////////////////

// Class to encapsulate the keyboard's functionality.
class CDIKeyboard : public CDirectInputDevice
{
private:
	unsigned char		m_ucKeysPressed[256];			//	Holds Keyboard Data.
	unsigned char		m_ucPrevKeysPressed[256];		//	Used for BufferedKeyEx.

	unsigned char		m_ucAsciiVals[256];		//	For CheckKeys and CheckBufferedKeys.
	HKL					m_keyLayout;			//	For CheckKeys and CheckBufferedKeys.



public:

	///////////////////////////////////////////////////////////////////
	//	Purpose	:	Clear the state of the keys. 
	void ClearKeys(void)	
	{ 
		//	clear the currently pressed keys
		memset(m_ucKeysPressed, 0, 256 * sizeof(unsigned char)); 
		//	clear the previously pressed keys
		memset(m_ucPrevKeysPressed, 0, 256 * sizeof(unsigned char));
	}

	CDIKeyboard(LPDIRECTINPUT8 pDI, HWND hWnd, bool bIsExclusive=false);
	bool ReadDevice(void);
	bool ReadBufferedDevice(void);

	///////////////////////////////////////////////////////////////////
	//	Return	:	true if it could acquire the device
	//	Purpose	:	To acquire the device when the application gains focus.
	//	NOTE	:	Also clears the state of the keys.
	bool Acquire(void)		{ ClearKeys(); return CDirectInputDevice::Acquire(); }
	
	///////////////////////////////////////////////////////////////////
	//	Return	:	true if it could unacquire the device
	//	Purpose	:	To unacquire the device when the application loses focus.
	//	NOTE	:	Also clears the state of the keys.
	bool Unacquire(void)	{ ClearKeys(); return CDirectInputDevice::Unacquire();  }
	bool KeyDown(unsigned char ucDIKey);
	bool KeyPressed(unsigned char ucDIKey);
	bool KeyPressedEx(unsigned char ucDIKey);
	bool KeyUp(unsigned char ucDIKey);
	bool KeyReleased(unsigned char ucDIKey); 
	bool KeyReleasedEx(unsigned char ucDIKey);
	char CheckKeys(void);
	char CheckBufferedKeysEx(void);
	unsigned char GetDIKCode(void);
	unsigned char GetBufferedDIKCodeEx(void);

};

//////////////////////////////////////////////////////////////////////////////////
//	CSGD_DIMouse
//////////////////////////////////////////////////////////////////////////////////

//	Class to encapsulate the mouse
class CDIMouse : public CDirectInputDevice
{
private:
	DIMOUSESTATE2	m_diMouseState;			//	Holds Mouse Data (button and movement info).
	DIMOUSESTATE2	m_diPrevMouseState;		//	Holds the previous state.
	
	int				m_nNumButtons;			//	Number of buttons supported.

	int				m_nPosX;				//	Helper positions for mouse cursor.
	int				m_nPosY;			

public:

	///////////////////////////////////////////////////////////////////
	//	Purpose	:	Clear the state of the buttons. 
	void ClearMouseButtons(void)	
	{ 
		//	clear the currently pressed keys
		memset(m_diMouseState.rgbButtons, 0, 8*sizeof(BYTE)); 
		//	clear the previously pressed keys
		memset(m_diPrevMouseState.rgbButtons, 0, 8*sizeof(BYTE));
	}

	CDIMouse(LPDIRECTINPUT8 pDI, HWND hWnd, bool bIsExclusive=false);
	bool ReadDevice(void);
	bool ReadBufferedDevice(void);
	bool ButtonDown(unsigned char ucButton);
	bool ButtonPressed(unsigned char ucButton);
	bool ButtonPressedEx(unsigned char ucButton);
	bool ButtonUp(unsigned char ucButton);
	bool ButtonReleased(unsigned char ucButton);
	bool ButtonReleasedEx(unsigned char ucButton);
	int CheckBufferedButtons(void);
	int	CheckBufferedButtonsEx(void);
	long MovementX(void);
	long MovementY(void);
	long WheelMovement(void);
	int GetNumButtons(void)		{ return m_nNumButtons; }
	int GetPosX(void)			{ return m_nPosX; }
	int GetPosY(void)			{ return m_nPosY; }
	void SetPosX(int nPosX)			{ m_nPosX = nPosX; }
	void SetPosY(int nPosY)			{ m_nPosY = nPosY; }

};	